第七章 数据管理
内存管理
在所有计算机系统中,内存都是一种稀缺资源。
Linux 程序决不允许直接访问内存,应用程序只是看起来好像可以这么做。
简单地内存分配
使用 malloc 函数来分配内存。
malloc 函数可以保证其返回的内存是地址对齐的,所以它可以被转换为任何类型的指针。
目前大多数 Linux 系统都使用32位整数和32位指针来指向内存,32位的指针可寻址的地址空间可达4GB 。并且有越来越多的64位 Linux 系统已经投入实际使用。
应用程序所分配的内存是由 Linux 内核管理的,每次应用程序请求内存或者尝试读写它已经分配的内存时,便会由 Linux 内核接管并决定如何处理这些请求。
刚开始的时候,内核只是通过使用空闲的物理内存来满足应用程序的内存请求,但是当物理内存耗尽时,它便会开始使用交换空间( swap space )。交换空间是在安装系统时分配的独立的磁盘区域。
内核会在物理内存和交换空间之间移动数据和程序代码,使得每次读写内存时,数据看起来总像是已存在于物理内存中。
更专业的说, Linux 实现了一个“按需换页的虚拟内存系统”。用户程序看到的内存全是虚拟的,它并不真正存在于程序使用的物理地址上。 Linux 将所有的内存都以页为单位进行划分,通常每一页大小为4096字节。每当程序试图访问内存时,就会发生虚拟内存到物理内存的转换,转换的具体实现和耗费时间取决于你所使用的特定硬件情况。
Linux 内核会对访问的内存地址进行检查,如果这个地址对于程序来说是合法可用的,内核就会确定需要向程序提供哪一个物理内存页面。
如果程序耗尽了物理内存和交换空间,或者当最大栈长度被超过时,内核将拒绝此后的内存请求,并可能提前终止程序的运行。
释放内存
Linux 内存管理系统完全有能力保证在程序结束时,把分配给它的内存返回给系统。但是,大多数程序还会动态使用内存。
动态使用内存的程序应该总是通过 free 调用把不用的内存释放掉。这样做可以把分散的内存块重新合并到一起。
其他内存分配函数
使用 calloc 和 realloc 也可以分配内存。
文件锁定
文件锁定是多用户、多任务操作系统中一个非常重要的组成部分。程序经常要共享数据,而这通常是通过文件来实现的。
Linux 提供了多种特性来实现文件锁定。其中最简单的办法是以原子操作的方式创建锁文件。所谓原子操作,就是在创建文件时,系统不允许任何其他事情发生。这就确保了创建的文件是唯一的。
第二种方法是锁定文件的一部分。
创建锁文件
许多应用程序只需要能够针对某个资源创建一个锁文件,然后,其他应用程序就可以通过检查这个文件来判断它们自己是否允许被访问这个资源。
Warning
锁文件仅仅只是充当指示器的角色,程序间需要通过相互协作来使用它们。用术语说,锁文件只是建议锁,而不是强制锁。
使用 open 调用可以创建这样的文件,只要带上 O_CREAT | O_EXCL 的标志即可。这样就通过原子操作完成两项工作:确定文件不存在,然后创建它。
区域锁定
有时候需要一些协调方法来提供对同一个文件的并发访问。
可以通过锁定文件区域的方法来处理这个问题,文件中的某个特定部分被锁定了,但其他文件可以访问这个文件的其他部分。
使用 fcntl 可以实现这个功能。锁定的信息由结构 flock 给出。
锁定状态下的读写操作
当对文件区域加锁之后,必须使用底层的 read 和 write 来访问文件中的数据,而不能使用更高级的 fread 和 fwrite 。这是因为高级函数会对读写数据进行缓存,缓存的数据可能在未来将会被加锁。
死锁
假设两个程序想要更新同一个文件,它们需要同时更新文件中的字节1和字节2。程序A选择首先更新字节2,然后再更新字节1 。程序B选择先更新字节1 ,然后才是字节2 。
若两个程序同时启动,程序A锁定字节2 ,程序B锁定字节1 。然后程序A试图锁定字节1 ,但会失败,A将等待。程序B尝试锁定字节2 ,也失败,程序B将等待。
于是两个程序都无法进行下去。这种情况称为死锁( deadlock )。 Linux 内核不能解开这样的死锁,这就要通过外部干涉,比如强制终止掉其中一个程序。
数据库
与使用文件来存储数据相比,使用数据库有如下方面的优势:
-
你可以存储长度可变的数据记录,这对平面的、非结构化的文件来说实现起来有点困难。
-
数据库使用索引来有效地存储和检索数据。这样做的一个显著优点是这个索引不必非得是一个简单的记录号——这在平面文件中很容易实现,它可以是一个任意的字符串。
dbm 数据库
Linux 带有一个基本的、但却非常高效的数据存储例程集,被称为 dbm 数据库。 dbm 数据库适合于存储相对比较静态的索引化数据。
dbm 是一个轻量级的数据库软件,无需安装独立的服务器(使用本地文件存储)。
dbm 数据库可以使用 key 存储可变长的数据结构,它适用于处理那些被频繁访问但却很少被更新的数据,因为它创建数据项时非常慢,而检索时非常快。
在我的 CentOS 7 上,这样获取:
$ sudo yum install gdbm-devel.x86_64
使用时,引入其头文件:ndbm.h
并加入链接选项:-lgdbm -lgdbm_compat
dbm 例程
dbm 数据库的基本元素是存储数据块和关键字数据块,每个数据都有一个唯一的 key 。这个数据块被定义为 datum 结构类型,它至少包含两个成员:
- void *dptr 数据空间地址
- size_t dsize 数据大小
dbm 使用一个 DBM 类型来访问数据库 API。当打开 dbm 数据库时,就会获取这个类型的变量的指针,然后会创建两个物理文件,它们的后缀分别是 .pag 和 .dir 。
dbm 访问函数
dbm_open dbm_close 打开/关闭数据库。
dbm_store dbm_fetch dbm_delete 存储记录/获取记录/删除记录。
dbm_firstkey dbm_nextkey 遍历数据库的每一个记录。